package com.ff.javamonkey.socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;

import android.util.Log;

import com.ff.javamonkey.socket.message.Message;
import com.ff.javamonkey.util.IOUtils;
import com.ff.javamonkey.util.StringUtils;

public class FTPClientConnect implements ClientConnect {
	// Debugging
	private final String TAG = "FTPClientConnect";
	
	// Connection variables.
	private String hostAddress;
	private static int hostPort = 21; // defaulted at 21
	
	// Socket status
	private boolean connected = false;
	private Socket s; // Not sure if it is a safe practice.
	private Socket sData;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	private InputStream inData;
	private OutputStream outData;
	private int code;
	private String message;
	private int status = 0;
	
	// File read Status
	private int readStatus = 0;
	
	// Passive status
	private boolean passive = false;

	public FTPClientConnect(String host) {
		this(host, hostPort);
	}
	
	public FTPClientConnect(String host, int port) {
		this.hostAddress = host;
		FTPClientConnect.hostPort = port;
	}
	
	/**
	 * Should be called after ClientConnect object is created. This method will attempt
	 * to make a FTP connection given the user and the password provided.
	 */
	public synchronized void connect(String user, String pass) throws IOException {
		if (s != null) {
			throw new IOException("is already connected. Disconnect first.");
		}
		message = "Establishing a connection with " + hostAddress;
		s = new Socket(hostAddress, hostPort);
		reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
		writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		
		String response = readLine();
		
		message = "Connection established. Sending parameters.";
		if (!response.startsWith("220 ")) {
			throw new IOException("An unkonwn response from FTP server: " + response);
		}

		message = "Sending USER info";
		sendLine("USER " + user);
		
		response = readLine();
		if (!response.startsWith("331 ")) {
			throw new IOException("An unknown reponse from FTP server after sending the user: " + response);
		}

		message = "Sending PASS info";
		sendLine("PASS " + pass);
		
		response = readLine();
		if (!response.startsWith("230 ")) {
			throw new IOException("Unable to log on with given pass: " + response);
		}

		message = "Sending Type BINARY";
		sendLine("TYPE I");
		
		response = readLine();
		if (!response.startsWith("200 ")) {
			throw new IOException("Unable to change into binary mode: " + response);
		}
		
		message = "Connection Successful";
		setConnected(true);
	}
	
	public synchronized int getFileSize(String filename) throws IOException {
		sendLine("SIZE " + filename);
		String response = "";
		Log.i(TAG, "Read " + filename + " in FTPClientConnect " + response);
		
		response = readLine();
		if (!response.startsWith("213 ")) {
			throw new IOException("Can't read the file size for " + filename + ": " + response);
		}
		
		while(true) {
			if (response != null) {
				int idxSpace = response.indexOf(' ');
				int idxMinus = response.indexOf('-');
				int idx = idxSpace < 0 ? idxMinus : idxMinus < 0 ? idxSpace : Math.min(idxSpace, idxMinus);
				if (idx < 0) {
					code = 0;
				}
				else {
					code = Integer.parseInt(response.substring(0, idx));
					response = response.substring(idx + 1);
				}
			}
			break;
		}
		return Integer.parseInt(response);
	}
	
	/**
	 * Read the socket to send a request to get a file, and read its response to write into a file.
	 * @param filename
	 * @return
	 * @throws IOException
	 */
	public synchronized byte[] getFile(String filename) throws IOException {
		passive();
		Log.i(TAG, filename + " called inside FTPClientConnect");
		ByteArrayOutputStream buff = new ByteArrayOutputStream();
		// if the supplied argument is a full path, change the directory first.
		String[] tokenized = filename.split("/");
		String response = "";
		for (int i = 0; i < tokenized.length-1; i++) {
			sendLine("CWD " + tokenized[i]);
			response = readLine();
			if (!response.startsWith("250 ")) {
				throw new IOException("Can't change into directory " + tokenized[i] + ": " + response);
			}
		}
		sendLine("RETR " + tokenized[tokenized.length-1]);
		response = readLine();
		Log.i(TAG, response + ": " + tokenized[tokenized.length-1]);
		IOUtils.copy(inData, buff, Long.MAX_VALUE, this);
		return buff.toByteArray();
	}
	
	public void passive() throws IOException {
		sendLine("PASV");
		String response = readLine();
		Log.i(TAG, "passive " + response);
		int first = response.indexOf('(') + 1;
		int last = response.indexOf(')');
		String[] address = StringUtils.arraySplit(response.substring(first, last), ',', true);
		// Build an IP Address
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < 4; i++) {
			builder.append(address[i]);
			if (i == 3) ;
			else {
				builder.append(".");
			}
		}
		String ip = builder.toString();
		InetAddress addr = InetAddress.getByName(ip);
		int port = (Integer.parseInt(address[4]) << 8) | Integer.parseInt(address[5]);
		sData = new Socket();
		sData.connect(new InetSocketAddress(addr, port),2000);
		inData = sData.getInputStream();
		// 227 Entering Passive Mode (74,86,148,171,233,83).
		Log.i(TAG, "PASV " + builder.toString() + ", " + port);
	}
	
	public void close() throws IOException {
		if (s != null) {
			sendLine("QUIT");
			String response = readLine();
			if (!response.startsWith("221 ")) {
				throw new IOException("Something wrong while trying to close: " + response);
			}
			s.close();
		}
	}
	
	/**
	 * Obvious. Read line from the returned socket's response.
	 * @return line returned socket's response.
	 * @throws IOException
	 */
	private String readLine() throws IOException {
		String line = reader.readLine();
		return line;
	}
	
	/**
	 * Sends a raw command to the FTP server.
	 * @param line the raw command sent.
	 * @throws IOException
	 */
	private void sendLine(String line) throws IOException {
		if (s == null) {
			throw new IOException("Not connected.");
		}
		try {
			writer.write(line + "\r\n");
			writer.flush();
		}
		catch (IOException ioe) {
			s = null;
			throw ioe;
		}
	} 
	
	// Getter and setter methods
	public String getHostAddress() {
		return hostAddress;
	}

	public void setHostAddress(String hostAddress) {
		this.hostAddress = hostAddress;
	}

	public boolean isConnected() {
		return connected;
	}

	public void setConnected(boolean connected) {
		this.connected = connected;
		if (this.connected) {
			setConnectionStatus(ClientConnect.CLIENT_CONNECTION_SUCCESSFUL);
		}
	}
	
	public String getMessage() {
		return this.message;
	}
	
	public int getConnectionStatus() {
		return this.status;
	}
	
	public void setConnectionStatus(int stat) {
		this.status = stat;
	}
	
	public int getReadStatus() {
		return this.readStatus;
	}
	
	public void setReadStatus(int stat) {
		this.readStatus = stat;
	}
	// End of getter and setter methods

	public void write(Message message) {
		// TODO Auto-generated method stub
		
	}

	public String read() {
		// TODO Auto-generated method stub
		return null;
	}

	public File getFile(String fileName, String fileOut) throws IOException {
		// TODO Auto-generated method stub
		return null;
	}

	public String[] getFileNames(String extension, String directory) {
		// TODO Auto-generated method stub
		return null;
	}

	public void connect(String host, int port) throws IOException {
		// TODO Auto-generated method stub
		
	}

	public void write(File file) {
		// TODO Auto-generated method stub
		
	}
}
